#ifndef SOCKET_CONNECTION_H_
#define SOCKET_CONNECTION_H_

#include "socket_address.h"

#include <memory>
#include <utility>
class ChannelProtocol;
class Description;
class SocketConnection;
class SocketCommand;
class SocketObserver
{
public:
    SocketObserver() = default;
    virtual ~SocketObserver() = default;
    virtual void Update(SocketCommand * command) = 0;
};
class SocketConnection
{
public:
    struct Message
    {
        uint8_t *     m_data{};
        int           m_data_length{};
        int           m_use_length{};
        SocketAddress m_peer_address{}; // for udp
    };
    struct Option
    {
        std::string m_host_name;
        bool        m_enable_nonblock{false};
        std::string m_ssl_key_log_file;
    };
    enum ConnectState
    {
        EmConnectStateInit = 0,
        EmConnectStateConnected,
        EmConnectStateConnecting,
        EmConnectStateTlsHandShake,
        EmConnectStateDisconnected,
        EmConnectStateError
    };
    enum ConnectionType
    {
        EmConnectionTypeInit   = 0,
        EmConnectionTypeTcp    = 1,
        EmConnectionTypeUdp    = 2,
        EmConnectionTypeTcpTls = 3,
        EmConnectionTypeUdpTls = 4
    };
public:
    explicit SocketConnection(const int type) : m_socket_connection_type(type)
    {
        m_connect_state = EmConnectStateInit;
    }
    virtual ~SocketConnection()                                     = default;
    virtual int  OpenSocket(SocketAddress   address, Option option) = 0;
    virtual int  AsyncConnect(SocketAddress address, Option option) = 0;
    virtual int  AsyncWrite(const uint8_t * data, int data_size)    = 0;
    virtual int  AsyncRead()                                        = 0;
    virtual void Close()                                            = 0;
    virtual int  AsyncWrite(const uint8_t * data, int length, SocketAddress & address){return 0;}
    const int ConnectState() const
    {
        return m_connect_state;
    }
    void UpdateConnectState(int state)
    {
        m_connect_state = state;
    }
    void RegisterSocketObserver(std::shared_ptr<SocketObserver> observer){m_observer = std::move(observer);}
    void RemoveSocketObserver(const std::shared_ptr<SocketObserver>& observer = nullptr){m_observer = nullptr;}
    bool Update(SocketCommand * command) const
    {
        if (m_observer && command)
        {
            m_observer->Update(command);
            return true;
        }
        return false;
    }
private:
    int m_connect_state{};
    int m_socket_connection_type{};
    std::shared_ptr<SocketObserver> m_observer;
};

class SocketCommand
{
public:
    enum EmCommandType
    {
        EmCmdConnect    = 0,
        EmCmdDisconnect = 1,
        EmCmdWrite      = 2,
        EmCmdRead       = 3,
        EmCmdError      = 4,
    };
    static SocketCommand CreateCommand(int cmd, std::shared_ptr<SocketConnection> connection, const SocketConnection::Message & message)
    {
        SocketCommand command = {};
        command.m_cmd = cmd;
        command.m_connection = std::move(connection);
        command.m_message    = message;
        return command;
    }
public:
    SocketCommand() = default;
    virtual ~SocketCommand() = default;
    const std::shared_ptr<SocketConnection> Connection() const {return m_connection;}
    const uint8_t * Data() const {return m_message.m_data;}
    const int       DataLength() const {return m_message.m_data_length;}
    const int       UseDataLength() const {return m_message.m_use_length;}
    const int       Cmd() const {return m_cmd;}
    void SetCmd(int cmd) {  m_cmd = cmd;}
    void SetUseDataLength(int length) {m_message.m_use_length = length;}
    void SetData(uint8_t * data, int length) {m_message.m_data = data, m_message.m_data_length = length;};
    void SetConnection(std::shared_ptr<SocketConnection> connection) {m_connection = std::move(connection);}
private:
    int m_cmd{};
    std::shared_ptr<SocketConnection> m_connection;
    SocketConnection::Message         m_message{};
};

#endif
